package cn.funeralobjects.ams.service.impl;

import cn.funeralobjects.ams.entity.*;
import cn.funeralobjects.ams.model.Product;
import cn.funeralobjects.ams.model.ProductApi;
import cn.funeralobjects.ams.model.ProductApiAccessDto;
import cn.funeralobjects.ams.model.RamRole;
import cn.funeralobjects.ams.repository.ERamRoleInfoRepository;
import cn.funeralobjects.ams.repository.ERamRoleRepository;
import cn.funeralobjects.ams.service.*;
import cn.funeralobjects.common.jpa.service.CommonCloudCrudJpaService;
import cn.funeralobjects.common.model.KeyValue;
import cn.funeralobjects.common.repository.CommonCloudRepository;
import cn.funeralobjects.common.service.annotataion.CommonRollback;
import cn.funeralobjects.common.service.annotataion.CommonService;
import cn.funeralobjects.common.service.exception.DataConflictException;
import cn.funeralobjects.common.service.exception.LackDataException;
import cn.funeralobjects.common.service.exception.OperationException;
import cn.funeralobjects.common.service.validate.DataCheck;
import cn.funeralobjects.util.Assert;
import cn.funeralobjects.util.NumberUtils;
import cn.funeralobjects.util.annotation.ArgHasLength;
import cn.funeralobjects.util.annotation.ArgNotZero;
import cn.funeralobjects.util.annotation.AssertArg;
import cn.funeralobjects.utils.aliyun.core.AliyunClientFactory;
import cn.funeralobjects.utils.aliyun.core.model.AliyunAction;
import cn.funeralobjects.utils.aliyun.core.model.AliyunClient;
import cn.funeralobjects.utils.aliyun.core.model.AliyunResponse;
import cn.funeralobjects.utils.aliyun.ram.RamAction;
import cn.funeralobjects.utils.aliyun.ram.RamProduct;
import cn.funeralobjects.utils.aliyun.ram.model.GetRoleData;
import cn.funeralobjects.utils.aliyun.ram.model.GetRoleParam;
import com.aliyuncs.exceptions.ClientException;
import com.querydsl.jpa.JPQLQueryFactory;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Optional;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;

/**
 * @author FuneralObjects
 * Create date: 2020/6/7 2:10 PM
 */
@CommonService
public class RamRoleServiceImpl implements RamRoleService, CommonCloudCrudJpaService<RamRole, ERamRole, Integer> {

    @Resource
    private ERamRoleRepository eRamRoleRepository;

    @Resource
    private JPQLQueryFactory jpqlQueryFactory;

    @Resource
    private ERamRoleInfoRepository eRamRoleInfoRepository;

    @Resource
    private ProductApiAccessService productApiAccessService;

    @Resource
    private ProductResService productResService;

    @Resource
    private ProductApiService productApiService;
    @Resource
    private ProductService productService;

    private EProduct ramProduct;
    private ConcurrentMap<AliyunAction, EProductApi> ramActionMap;

    @Override
    public CommonCloudRepository<ERamRole, Integer> commonCloudRepository() {
        return eRamRoleRepository;
    }

    @PostConstruct
    public void initProduct() {
        ramProduct = productService.findByCode(RamProduct.CODE)
                .orElseGet(() -> productService.add(new Product().setCode(RamProduct.CODE).setDescription(RamProduct.DESCRIPTION)));

        ramActionMap = RamProduct.ACTIONS
                .stream()
                .map(action -> {
                    EProductApi api = productApiService.findOne(ramProduct.getId(), action.getAction(), action.getVersion())
                            .orElseGet(() -> productApiService.add(new ProductApi()
                                    .setAction(action.getAction())
                                    .setDomain(action.getDomain())
                                    .setMethod(action.getMethod())
                                    .setName(action.getName())
                                    .setProtocol(action.getProtocol())
                                    .setVersion(action.getVersion())
                                    .setProductId(ramProduct.getId())
                            ));
                    return new KeyValue<>(action, api);
                }).collect(Collectors.toConcurrentMap(KeyValue::getKey, KeyValue::getValue));
    }

    @CommonRollback
    @Override
    public ERamRole add(@AssertArg RamRole ramRole) {
        this.checkConflict(ramRole);
        ERamRole entity = this.toEntity(ramRole);
        ProductApiAccessDto apiAccessDto = productApiAccessService.getProductApiAccessKey(ramRole.getResProdId(), RamAction.GET_ROLE.getAction(), RamAction.GET_ROLE.getVersion()).orElseThrow(() -> new LackDataException(String.format("The AccessKey does't exists of ProductRes[%s]", ramRole.getResProdId()), productResService.createDataCode("id", String.valueOf(ramRole.getResProdId()))));
        AliyunClientFactory factory = AliyunClientFactory.builder().setAccessKeyId(apiAccessDto.getAccessKeyId())
                .setAccessKeySecret(apiAccessDto.getAccessKeySecret())
                .build(apiAccessDto.getRegionId());
        AliyunClient client = factory.buildClient(RamAction.GET_ROLE);
        try {
            AliyunResponse<GetRoleData> response = client.doAction(new GetRoleParam(ramRole.getRoleName()));
            entity = eRamRoleRepository.save(entity);
            GetRoleData.Role roleInfo = response.getData().getRole();
            ERamRoleInfo eRamRoleInfo = new ERamRoleInfo()
                    .setRamRoleId(entity.getId())
                    .setAssumeRolePolicyDocument(roleInfo.getAssumeRolePolicyDocument())
                    .setRoleName(roleInfo.getRoleName())
                    .setArn(roleInfo.getArn())
                    .setCreateDate(roleInfo.getCreateDate())
                    .setDescription(roleInfo.getDescription())
                    .setRoleId(roleInfo.getRoleId())
                    .setMaxSessionDuration(roleInfo.getMaxSessionDuration())
                    .setUpdateDate(roleInfo.getUpdateDate());
            eRamRoleInfoRepository.save(eRamRoleInfo);

        } catch (ClientException e) {
            throw new OperationException("The aliyun client error", e);
        }

        return entity;
    }

    @AssertArg
    @Override
    public void checkConflict(@AssertArg RamRole ramRole) throws DataConflictException {
        this.mustNotConflictByName(ramRole.getName());
        DataCheck.conflictData(this.existsRoleNameInProdRes(ramRole.getRoleName(), ramRole.getResProdId()), () -> createDataCode("roleName", ramRole.getRoleName()));
    }

    @Override
    public ERamRole toEntity(RamRole ramRole) {
        ERamRole eRamRole = new ERamRole();
        eRamRole.setProdResId(ramRole.getResProdId())
                .setCode(ramRole.getCode())
                .setName(ramRole.getName());
        return eRamRole;
    }

    @AssertArg
    @Override
    public void checkConflict(@AssertArg RamRole ramRole, @ArgNotZero Integer withoutId) throws DataConflictException {
        this.mustNotConflictByName(ramRole.getName(), withoutId);
    }

    @Override
    public void copyToEntity(RamRole sourceModify, ERamRole targetEntity) {
        targetEntity.setProdResId(sourceModify.getResProdId())
                .setCode(targetEntity.getCode())
                .setName(targetEntity.getName());
    }

    @Override
    public boolean isValid(Integer id) {
        return NumberUtils.notZero(id);
    }

    @Override
    public void validateModify(RamRole ramRole) {
        Assert.argAssert(ramRole, "ramRole");
    }

    @AssertArg
    @Override
    public boolean existsRoleNameInProdRes(@ArgHasLength String roleName, @ArgNotZero Integer prodResId) {
        return jpqlQueryFactory.selectFrom(QERamRole.eRamRole)
                .join(QERamRoleInfo.eRamRoleInfo).on(QERamRole.eRamRole.id.eq(QERamRoleInfo.eRamRoleInfo.ramRoleId))
                .where(
                        QERamRole.eRamRole.delSalt.eq(getExistsDelSalt())
                                .and(QERamRoleInfo.eRamRoleInfo.roleName.eq(roleName))
                                .and(QERamRole.eRamRole.prodResId.eq(prodResId))
                ).fetchCount() > 0;
    }

    @Override
    public Optional<ERamRoleInfo> getRamInfoById(Integer id) {
        return eRamRoleInfoRepository.findById(id);
    }

    private Integer getExistsDelSalt() {
        return (Integer) eRamRoleRepository.getDefaultExistsDelSalt();
    }
}
